#include "connection.hpp"

#include "../lge2d.hpp"

namespace lge2d
{
namespace lan
{

//------------------------------------------------------------------------------
Connection::Connection() :
	m_state(Disconnected),
	m_last_error(NoError),
	m_server_socket(false),
	m_connecting_timeout(0.f)
{

}

//------------------------------------------------------------------------------
Connection::Connection(sf::SocketTCP& socket,
				float timeOut /* = CLIENT_CONNECTIONG_TIMEOUT */) :
	m_state(Disconnected),
	m_last_error(NoError),
	m_server_socket(true),
	m_connecting_timeout(timeOut)
{
	m_socket = socket;
	if (m_socket.IsValid())
	{
		// отключаем блокирование для сокета
		m_socket.SetBlocking(false);
		//
		m_state = Validating;
		// отправка пакета handshake
		Packet packet;
		packet << CONNECTION_PASS_KEY;
		sf::Socket::Status status = m_socket.Send(packet);

		if (status == sf::Socket::Done)
		{
			lge2d::log->Info("[SERVER] send connect handshake packet.");
		}
		else
		{
			lge2d::log->Error("[SERVER] can't send handshake packet.");
			m_socket.Close();
			m_state = Disconnected;
			m_last_error = Error;
		}
	}
	else
	{
		m_state = Disconnected;
		m_last_error = Error;
	}
}

//------------------------------------------------------------------------------
Connection::~Connection()
{
	Disconnect();
}

//------------------------------------------------------------------------------
bool Connection::Update(float dt)
{
	bool res(true);

	switch (m_state)
	{
	// В даннос состоянии находится клиент, ожидающий
	// подтверждения валидности соединения от сервера
	case Connecting:
	// В данном состоянии находится серверный
	// connection, ожидая ответного пакета от клиента
	case Validating:
	{
		Packet packet;
		sf::Socket::Status status = m_socket.Receive(packet);
		switch(status)
		{
		/// The socket is not ready to send / receive data yet
		case sf::Socket::NotReady:
			if (m_connecting_timeout > 0.f)
			{
				m_connecting_timeout -= dt;

				if (m_connecting_timeout < 0.f)
				{
					m_connecting_timeout = 0.f;
					m_state = Disconnected;
					m_last_error = ConnectingTimeOut;
					m_socket.Close();
					lge2d::log->Info("Socket closed by connecting timeout.");
				}
			}
			break;

		/// The socket has sent / received the data
		case sf::Socket::Done:
		{
			lge2d::log->Info("Socket receive data.");
			unsigned connect_packet(0);
			packet >> connect_packet;
			if (connect_packet == CONNECTION_PASS_KEY)
			{
				// если соединение клинтское - отправить ответ на сервер
				// иначе будет отключен по таймауту
				if (!m_server_socket)
				{
					Packet response;
					response << CONNECTION_PASS_KEY;
					sf::Socket::Status responceStatus = m_socket.Send(response);

					if (responceStatus != sf::Socket::Done)
					{
						m_state = Disconnected;
						m_connecting_timeout = 0.f;
						res = false;
					}

					switch (responceStatus)
					{
					case sf::Socket::Done:
						m_state = Connected;
						lge2d::log->Info("Socket now connected to server.");
						break;

					case sf::Socket::NotReady:
						m_last_error = Error;
						lge2d::log->Error("Can't send response.Socket send not ready");
						break;
					case sf::Socket::Disconnected:
						m_last_error = ConnectionLost;
						lge2d::log->Error("Can't send response. Disconnected.");
						break;
					case sf::Socket::Error:
						m_last_error = Error;
						lge2d::log->Error("Can't send response. Unexpected socket error");
						break;
					}
				}
				else
				{
					m_state = Connected;
					lge2d::log->Info("[SERVER] Socket now connected.");
				}
			}
			else
			{
				m_state = Disconnected;
				m_last_error = NotValidated;
				m_connecting_timeout = 0.f;
				res = false;
				lge2d::log->Info("Connection not validated.");
			}
			break;
		}

		/// The TCP socket has been disconnected
		case sf::Socket::Disconnected:
			m_connecting_timeout = 0.f;
			m_state = Disconnected;
			m_last_error = ConnectionLost;
			res = false;
			lge2d::log->Info("Socket Disconnected.");
			break;

		/// An unexpected error happened
		case sf::Socket::Error:
			m_connecting_timeout = 0.f;
			m_state = Disconnected;
			m_last_error = Error;
			res = false;
			lge2d::log->Info("Unexpected socket error.");
			break;
		}

		break;
	}

	break;

	case Connected:
	{
		Packet packet;
		bool loop(true);
		sf::Socket::Status status;

		for(unsigned i = 0; i < SOCKET_MAX_PACKETS_COUNT && loop; ++i, loop = false)
		{
			status = m_socket.Receive(packet);

			switch(status)
			{
			/// The socket is not ready to send / receive data yet
			case sf::Socket::NotReady:
				break;
			/// The socket has sent / received the data
			case sf::Socket::Done:
				loop = true;
				m_input_packets.push_back(packet);
				m_statistic.totalPacktesReceived++;
				m_statistic.totalBytesReceived += packet.GetDataSize();
				break;

			/// The TCP socket has been disconnected
			case sf::Socket::Disconnected:
				m_connecting_timeout = 0.f;
				m_state = Disconnected;
				m_last_error = ConnectionLost;
				res = false;
				lge2d::log->Info("Socket Disconnected.");
				break;

			/// An unexpected error happened
			case sf::Socket::Error:
				m_connecting_timeout = 0.f;
				m_state = Disconnected;
				m_last_error = Error;
				res = false;
				lge2d::log->Info("Unexpected socket error.");
				break;
			}
		}
		// что бы не писать if используем значение результата выполнения ранее
		loop = res;
		unsigned to_send_count = std::min(SOCKET_MAX_PACKETS_COUNT, m_output_packets.size());
		unsigned send_cout(0);

		for(unsigned i = 0; i < to_send_count && loop; ++i)
		{
			loop = false;
			status = m_socket.Send(m_output_packets[i]);
			switch(status)
			{
			/// The socket is not ready to send / receive data yet
			case sf::Socket::NotReady:
				break;

			/// The socket has sent / received the data
			case sf::Socket::Done:
				loop = true;
				++send_cout;
				m_statistic.totalPacktesSent++;
				m_statistic.totalBytesSent += m_output_packets[i].GetDataSize();
				break;

			/// The TCP socket has been disconnected
			case sf::Socket::Disconnected:
				m_connecting_timeout = 0.f;
				m_state = Disconnected;
				m_last_error = ConnectionLost;
				res = false;
				lge2d::log->Info("Socket Disconnected.");
				break;

			/// An unexpected error happened
			case sf::Socket::Error:
				m_connecting_timeout = 0.f;
				m_state = Disconnected;
				m_last_error = Error;
				res = false;
				lge2d::log->Info("Unexpected socket error.");
				break;
			}
		}

		// удаление отправленных пакетов из очереди.
		if (send_cout > 0)
		{
			m_output_packets.erase(m_output_packets.begin(),
								m_output_packets.begin() + send_cout);
		}

		break;
	}

	case Disconnected:
		res = false;
		break;
	}

	return res;
}

//------------------------------------------------------------------------------
Connection::State Connection::GetState() const
{
	return m_state;
}

//------------------------------------------------------------------------------
Connection::ErrorCode Connection::GetLastError() const
{
	return m_last_error;
}

//------------------------------------------------------------------------------
const Connection::Statictics& Connection::GetStatistics() const
{
	return m_statistic;
}

//------------------------------------------------------------------------------
void Connection::Send(Packet& packet)
{
	if (m_state != Connected)
		return;
	/* TODO: ограничение размера очереди.
	 Так как в случае неудачной отправки возможны проблемы с
	 выделением больших объемов памяти.
	*/
	m_output_packets.push_back(packet);
}

size_t Connection::GetSendCount() const
{
	return m_output_packets.size();
}

//------------------------------------------------------------------------------
size_t Connection::GetReceivedCount() const
{
	return m_input_packets.size();
}

const Packet& Connection::Get(size_t idx) const
{
	return m_input_packets[idx];
}

void Connection::ClearReceived()
{
	m_input_packets.clear();
}

//------------------------------------------------------------------------------
Connection::ErrorCode Connection::Connect(const std::string& host,
		unsigned short port /* = DEFAULT_SERVER_PORT */,
		float timeOut /* = CLIENT_CONNECTIONG_TIMEOUT */)
{
	m_last_error = NoError;

	if (m_state != Disconnected)
	{
		m_last_error = AlreadyConnected;
		lge2d::log->Error("Already connected.");
		return m_last_error;
	}

	sf::IPAddress ipAddress;
	if (host.length() == 0)
	{
		ipAddress = sf::IPAddress::LocalHost;
	}
	else
	{
		ipAddress = sf::IPAddress(host);
	}

	lge2d::log->Info("Try connection to host: %s (%s:%d)",
			host.c_str(),
			ipAddress.ToString().c_str(),
			port);

	if (!ipAddress.IsValid())
	{
		m_last_error = InvalidHostAddress;
		lge2d::log->Error("Host not found.");
		return m_last_error;
	}

	sf::SocketTCP socket;
	socket.SetBlocking(false);

	m_connecting_timeout = timeOut;
	sf::Socket::Status status = socket.Connect(port, ipAddress);

	switch (status)
	{
	/// The socket has sent / received the data
	case sf::Socket::Done:
	/// The socket is not ready to send / receive data yet
	case sf::Socket::NotReady:
		m_state = Connecting;
		m_socket = socket;
		lge2d::log->Info("Connecting...");
		break;

	/// The TCP socket has been disconnected
	case sf::Socket::Disconnected:
		lge2d::log->Info("Disconnected.");
		m_state = Disconnected;
		m_last_error = ConnectionLost;
		break;

	case sf::Socket::Error:
		lge2d::log->Info("Unexpected socket error");
		m_state = Disconnected;
		m_last_error = Error;
		break;
	}

	return m_last_error;
}

//------------------------------------------------------------------------------
void Connection::Disconnect()
{
	if (m_state == Disconnected)
		return;

	m_state = Disconnected;
	if (m_socket.IsValid())
		m_socket.Close();

	m_input_packets.clear();
	m_output_packets.clear();
}

} /* namespace lan */
} /* namespace lge2d */
